import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

public class DrawingAnalyzer {
	private static class VisualElementInfo {
		private enum EPositioningScheme {
			NORMAL_FLOW,
			FLOATS,
			ABSOLUTE;
		}
		private enum EBlockType {
			NONE,
			BLOCK_LEVEL,
			INLINE_LEVEL,
			TABLE_RELATED_ELEMENT
		}
		public Boolean root = null;
		public Boolean rootRelated = null;
		public Boolean positioned = null;
		public Integer z = null;
		public EPositioningScheme posScheme = null;
		public Boolean createNewSC = null;
		public EBlockType blockType = null;
		public Boolean tableElement = null;
		public IProcedure<Integer> setDrawID = null;
		public IProcedure<Integer> setLayerID = null;
		public boolean isDisplayNone() {
			return (blockType == EBlockType.NONE);
		}
		public static VisualElementInfo getInstanceForElement(final String elName
				, final String cssPosition, final String cssZIndex, final String cssFloat, final String cssDisplay
				, final IProcedure<Integer> setDrawID, final IProcedure<Integer> setLayerID) {
			final VisualElementInfo rez = new VisualElementInfo();
						if (cssDisplay.equalsIgnoreCase("none"))
							rez.blockType = EBlockType.NONE;
						else if ( cssDisplay.equalsIgnoreCase("block")
									|| cssDisplay.equalsIgnoreCase("list-item")
									|| cssDisplay.equalsIgnoreCase("table")
								)
							rez.blockType = EBlockType.BLOCK_LEVEL;
						else if ( cssDisplay.equalsIgnoreCase("inline")
									|| cssDisplay.equalsIgnoreCase("inline-block")
									|| cssDisplay.equalsIgnoreCase("inline-table")
								)
							rez.blockType = EBlockType.INLINE_LEVEL;
						else if ( cssDisplay.equalsIgnoreCase("table-row")
								|| cssDisplay.equalsIgnoreCase("table-row-group")
								|| cssDisplay.equalsIgnoreCase("table-header-group")
								|| cssDisplay.equalsIgnoreCase("table-footer-group")
								|| cssDisplay.equalsIgnoreCase("table-column")
								|| cssDisplay.equalsIgnoreCase("table-column-group")
								|| cssDisplay.equalsIgnoreCase("table-cell")
								|| cssDisplay.equalsIgnoreCase("table-caption")
							)
							rez.blockType = EBlockType.TABLE_RELATED_ELEMENT;
			if (rez.isDisplayNone())
				return rez;
			if ( elName.equalsIgnoreCase("HTML") )
				rez.root = true;
			else
				rez.root = false;
			if (elName.equalsIgnoreCase("BODY"))
				rez.rootRelated = true;
			else
				rez.rootRelated = false;
			rez.positioned = (cssPosition.equalsIgnoreCase("static"))? false:true;
			if (!cssZIndex.equalsIgnoreCase("auto")) {
				try {
				rez.z = Integer.valueOf(cssZIndex);
				} catch(final NumberFormatException e) {
					rez.z = null;
				}
			}
			boolean floatBool = !"none".equalsIgnoreCase(cssFloat);
			if ( cssPosition.equalsIgnoreCase("static")
					|| cssPosition.equalsIgnoreCase("relative")
					)
				rez.posScheme = EPositioningScheme.NORMAL_FLOW;
			else if ( cssPosition.equalsIgnoreCase("absolute")
						|| cssPosition.equalsIgnoreCase("fixed")
					)
				rez.posScheme = EPositioningScheme.ABSOLUTE;
			else if (floatBool)
				rez.posScheme = EPositioningScheme.FLOATS;
			if (rez.positioned && rez.z!=null
					)
				rez.createNewSC = true;
			else
				rez.createNewSC = false;
			if (  rez.blockType == EBlockType.TABLE_RELATED_ELEMENT
					|| cssDisplay.equalsIgnoreCase("table")
					|| cssDisplay.equalsIgnoreCase("inline-table") )
					rez.tableElement = true;
			else
				rez.tableElement = false;
			rez.setDrawID = setDrawID;
			rez.setLayerID = setLayerID;
			return rez;
		}
	}
	private static class StackingContext {
		public static enum EStackingContextType {
			STACKING_CONTEXT,
			SEMI_STACKING_CONTEXT,
			FICTIVE_STACKING_CONTEXT
		}
		public final EStackingContextType stackingContextType;
		public final VisualElementInfo visualElementInfo;
		public boolean hasSeveralDescSCGroup = false;
		public boolean hasSeveralChildSCGroup = false;
		public List<StackingContext> rootRelatedSC;
		public List<StackingContext> negativeSC;
		public List<StackingContext> normalFlowBlockBoxes;
		public List<StackingContext> floatBoxes;
		public List<StackingContext> normalFlowInlineBoxes;
		public List<StackingContext> zeroOrAutoSC;
		public List<StackingContext> positiveSC;
		public StackingContext(final EStackingContextType stackingContextType, final VisualElementInfo visualElement
				, final StackingContext parentSC) {
			this.stackingContextType = stackingContextType;
			this.visualElementInfo = visualElement;
			switch (stackingContextType) {
			case STACKING_CONTEXT:
				rootRelatedSC = new LinkedList<StackingContext>();
				negativeSC = new SortedStackingContextList();
				normalFlowBlockBoxes = new LinkedList<StackingContext>();
				floatBoxes = new LinkedList<StackingContext>();
				normalFlowInlineBoxes = new LinkedList<StackingContext>();
				zeroOrAutoSC = new LinkedList<StackingContext>();
				positiveSC = new SortedStackingContextList();
				break;
			case SEMI_STACKING_CONTEXT:
				rootRelatedSC = null;
				negativeSC = parentSC.negativeSC;
				normalFlowBlockBoxes = new LinkedList<StackingContext>();
				floatBoxes = new LinkedList<StackingContext>();
				normalFlowInlineBoxes = new LinkedList<StackingContext>();
				zeroOrAutoSC = parentSC.zeroOrAutoSC;
				positiveSC = parentSC.positiveSC;
				break;
			case FICTIVE_STACKING_CONTEXT:
				rootRelatedSC = null;
				negativeSC = null;
				normalFlowBlockBoxes = null;
				floatBoxes = null;
				normalFlowInlineBoxes = null;
				zeroOrAutoSC = null;
				positiveSC = null;
				break;
			}
		}
	}
	private static class SortedStackingContextList extends LinkedList<StackingContext> {
		private static final long serialVersionUID = -8000554838996115820L;
		public void addSorted(final StackingContext sc) {
			ListIterator<StackingContext> iter = this.listIterator();
			boolean found = false;
			while (!found && iter.hasNext()) {
				final StackingContext scTmp = iter.next();
				if ( (sc.visualElementInfo.z == null /*auto*/ && scTmp.visualElementInfo.z != null && scTmp.visualElementInfo.z >0)
						|| (sc.visualElementInfo.z != null && scTmp.visualElementInfo.z != null && 
								sc.visualElementInfo.z < scTmp.visualElementInfo.z) )
					found = true;
			}
			if (found)
				iter.previous();
			iter.add(sc);
		}
		@Override
		public boolean add(final StackingContext sc) {
			addSorted(sc);
			return true;
		}
	}
	private static enum EVisualElementType {
		ROOT_RELATED,
		POSITIONED_NEGATIVE_SC,
		NORMAL_FLOW_BLOCK_BOX,
		NORMAL_FLOW_BLOCK_BOX_SC,
		FLOAT_BOX,
		FLOAT_BOX_SC,
		NORMAL_FLOW_INLINE_BOX,
		NORMAL_FLOW_INLINE_BOX_SC,
		POSITIONED_AUTO_SC,
		POSITIONED_AUTO,
		POSITIONED_ZERO_SC,
		POSITIONED_POSITIVE_SC
	}
	private final boolean createDrawId;
	private final boolean createLayerId;
	private final List<StackingContext> orderOfCreatingSC = new ArrayList<StackingContext>(10000);
	private int currSCIndex;
	private final List<List<StackingContext>> frameTreeIndeces = new ArrayList<List<StackingContext>>(5);
	private int frameLayerIndex = -1;
	public DrawingAnalyzer(boolean createDrawId, boolean createLayerId) {
		this.createDrawId = createDrawId;
		this.createLayerId = createLayerId;
		currSCIndex = -1;
	}
	private static final EVisualElementType getElType(VisualElementInfo ve) {
		if (ve.rootRelated)
			return EVisualElementType.ROOT_RELATED;
		if (ve.positioned && ve.z !=null && ve.z<0)
			return EVisualElementType.POSITIONED_NEGATIVE_SC;
		if (!ve.positioned && ve.posScheme == VisualElementInfo.EPositioningScheme.NORMAL_FLOW
				&& (ve.blockType == VisualElementInfo.EBlockType.BLOCK_LEVEL
						||  ve.blockType == VisualElementInfo.EBlockType.TABLE_RELATED_ELEMENT)) {
			if (ve.createNewSC)
				return EVisualElementType.NORMAL_FLOW_BLOCK_BOX_SC;
			else
				return EVisualElementType.NORMAL_FLOW_BLOCK_BOX;
		}
		if (!ve.positioned && ve.posScheme == VisualElementInfo.EPositioningScheme.FLOATS) {
			if (ve.createNewSC)
				return EVisualElementType.FLOAT_BOX_SC;
			else 
				return EVisualElementType.FLOAT_BOX;
		}
		if (!ve.positioned && ve.posScheme == VisualElementInfo.EPositioningScheme.NORMAL_FLOW
				&& ve.blockType == VisualElementInfo.EBlockType.INLINE_LEVEL) {
			if (ve.createNewSC)
				return EVisualElementType.NORMAL_FLOW_INLINE_BOX_SC;
			else
					return EVisualElementType.NORMAL_FLOW_INLINE_BOX;
		}
		if (ve.positioned && ve.z ==null) {
			if (ve.createNewSC)
				return EVisualElementType.POSITIONED_AUTO_SC;
			else
				return EVisualElementType.POSITIONED_AUTO;
		}
		if (ve.positioned && ve.z !=null && ve.z == 0)
			return EVisualElementType.POSITIONED_ZERO_SC;
		
		if (ve.positioned && ve.z !=null && ve.z > 0)
			return EVisualElementType.POSITIONED_POSITIVE_SC;
		return null;
	}
	private static final List<StackingContext> getCorrespondingList(final EVisualElementType vet, final StackingContext sc) {
		switch (vet) {
		case ROOT_RELATED:
			return sc.rootRelatedSC;
		case POSITIONED_NEGATIVE_SC:
			return sc.negativeSC;
		case NORMAL_FLOW_BLOCK_BOX:
			return sc.normalFlowBlockBoxes;
		case FLOAT_BOX:
			return sc.floatBoxes;
		case NORMAL_FLOW_INLINE_BOX:
			return sc.normalFlowInlineBoxes;
		case NORMAL_FLOW_BLOCK_BOX_SC:
		case FLOAT_BOX_SC:
		case NORMAL_FLOW_INLINE_BOX_SC:
		case POSITIONED_AUTO:
		case POSITIONED_ZERO_SC:
			return sc.zeroOrAutoSC;
		case POSITIONED_POSITIVE_SC:
			return sc.positiveSC;
		default:
			return null;
		}
	}
	public final Object goDown(final String elName
			, final String cssPosition, final String cssZIndex, final String cssFloat, final String cssDisplay
			, final IProcedure<Integer> setDrawID, final IProcedure<Integer> setLayerID) {
		if (!createDrawId && !createLayerId)
			return null;
		final VisualElementInfo ve = VisualElementInfo.getInstanceForElement(elName, cssPosition, cssZIndex, cssFloat
				, cssDisplay, setDrawID, setLayerID);
		if (ve.isDisplayNone())
			return null;
		StackingContext sc = null;
		
		if (ve.root) {
			sc = new StackingContext(StackingContext.EStackingContextType.STACKING_CONTEXT, ve, null);
			
			frameLayerIndex++;
			if (frameTreeIndeces.size() == frameLayerIndex) // last element +1 in the frameTreeIndeces
				frameTreeIndeces.add(new ArrayList<StackingContext>(10));
			frameTreeIndeces.get(frameLayerIndex).add(sc);
		}
		else 
		{
			final EVisualElementType vet = getElType(ve);
			if (ve.tableElement)
			sc = new StackingContext(StackingContext.EStackingContextType.SEMI_STACKING_CONTEXT, ve, orderOfCreatingSC.get(currSCIndex));
			else {
				switch (vet) {
				case POSITIONED_NEGATIVE_SC:
				case NORMAL_FLOW_BLOCK_BOX_SC:
				case FLOAT_BOX_SC:
				case NORMAL_FLOW_INLINE_BOX_SC:
				case POSITIONED_AUTO_SC:
				case POSITIONED_ZERO_SC:
				case POSITIONED_POSITIVE_SC:
					sc = new StackingContext(StackingContext.EStackingContextType.STACKING_CONTEXT, ve, null);
					break;
				case FLOAT_BOX:
				case POSITIONED_AUTO:
					sc = new StackingContext(StackingContext.EStackingContextType.SEMI_STACKING_CONTEXT, ve, orderOfCreatingSC.get(currSCIndex));
					break;
				case ROOT_RELATED:
				case NORMAL_FLOW_BLOCK_BOX:
				case NORMAL_FLOW_INLINE_BOX:
					sc = new StackingContext(StackingContext.EStackingContextType.FICTIVE_STACKING_CONTEXT, ve, orderOfCreatingSC.get(currSCIndex));
					break;
				}
			}
			getCorrespondingList(vet, orderOfCreatingSC.get(currSCIndex)).add(sc);
		}
		if (sc.stackingContextType != StackingContext.EStackingContextType.FICTIVE_STACKING_CONTEXT) {
			orderOfCreatingSC.add(sc);
			currSCIndex++;
		}
		return sc;
	}
	private boolean _sameZ(final Integer z1, final Integer z2) {
		if (z1 == null && z2 == null)
			return true;
		if (z1 == null || z2 == null)
			return false;
		return z1.equals(z2);
	}
	private boolean _scHasSeveralSCGroups(final StackingContext sc, int mode) {
		final int potentialGroupsNum = (int)(Math.signum(sc.rootRelatedSC.size()) + Math.signum(sc.negativeSC.size())
				+ Math.signum(sc.normalFlowBlockBoxes.size() + sc.floatBoxes.size() + sc.normalFlowInlineBoxes.size())
				+ Math.signum(sc.zeroOrAutoSC.size()) + Math.signum(sc.positiveSC.size()));
		if (potentialGroupsNum == 0)
			return false;
		if (potentialGroupsNum > 1)
			return true;
		if (sc.rootRelatedSC.size()>0) {
			return false;
		}
		if (sc.normalFlowBlockBoxes.size() + sc.floatBoxes.size() + sc.normalFlowInlineBoxes.size() >0)
			if (sc.visualElementInfo.root)
				return true;
			else
				return false;
		if (sc.visualElementInfo.root)
			return true;
		List<StackingContext> scList = null;
		if (sc.negativeSC.size() > 0)
			scList = sc.negativeSC;
		else if (sc.zeroOrAutoSC.size() > 0)
			scList = sc.zeroOrAutoSC;
		else if (sc.positiveSC.size() > 0)
			scList = sc.positiveSC;
		if (scList.size() == 1) {
			if (mode == 0)
				return false;
			else
				return scList.get(0).hasSeveralDescSCGroup;
		}
		final Iterator<StackingContext> iter = scList.iterator();
		final StackingContext sc1 = iter.next();
		if (sc1.hasSeveralDescSCGroup)
			return true;
		while (iter.hasNext()) {
			final StackingContext sc2 = iter.next();
			if (!_sameZ(sc1.visualElementInfo.z, sc2.visualElementInfo.z) || sc2.hasSeveralDescSCGroup) {
				return true;
			}
		}
		return false;
	}
	public void goUp(final Object id) {
		if (id == null)
			return;
		final StackingContext sc = orderOfCreatingSC.get(currSCIndex);
		if (sc == id) {
			if (sc.stackingContextType == StackingContext.EStackingContextType.STACKING_CONTEXT) {
				sc.hasSeveralChildSCGroup = _scHasSeveralSCGroups(sc, 0);
				sc.hasSeveralDescSCGroup = _scHasSeveralSCGroups(sc, 1);
			}
			if (sc.visualElementInfo.root) {
				frameLayerIndex--;
			}
			orderOfCreatingSC.remove(currSCIndex);
			currSCIndex--;
		}
	}
	public void compute() {
		if (!createDrawId && !createLayerId)
			return;
		for (int i=frameTreeIndeces.size()-1; i>=0; i--) {
			final Iterator<StackingContext> iter = frameTreeIndeces.get(i).iterator();
			while (iter.hasNext()) {
			goThroughSCOfDOMTree(iter.next());
			}
		}
	}
	private int currDrawId = 0;
	private boolean drawIdUsed = false;
	private int currLayerId = 0;
	private boolean layerIdUsed = false;
	private final boolean _setDrawId(VisualElementInfo ve, int id) {
		if (createDrawId) {
			ve.setDrawID.apply(id);
			return true;
		}
		return false;
	}
	private final boolean _setLayerId(VisualElementInfo ve, int id) {
		if (createLayerId) {
			ve.setLayerID.apply(id);
			return true;
		}
		return false;
	}
	private void _goThroughSCList(List<StackingContext> list) {
		final Iterator<StackingContext> iter = list.iterator();
		while (iter.hasNext()) {
			goThroughSCOfDOMTree(iter.next());
		}
	}
	private void _goThroughRootRelatedGroup(final List<StackingContext> list) {
		if (list.size() != 0) {
			_goThroughSCList(list);
		}
	}
	private void _goThroughInflowFloatingRelatedGroup(final List<StackingContext> inlines, final List<StackingContext> floats
			, final List<StackingContext> blocks) {
		_goThroughSCList(inlines);
		_goThroughSCList(floats);
		_goThroughSCList(blocks);
		
	}
	private void goThroughPositionedGroups(final List<StackingContext> list) {
		if (list.size() != 0) {
			final Iterator<StackingContext> iter = list.iterator();
			StackingContext prevSC = null;
			while (iter.hasNext()) {
				final StackingContext sc = iter.next();
				if (prevSC != null && (!_sameZ(prevSC.visualElementInfo.z, sc.visualElementInfo.z)
						|| prevSC.hasSeveralDescSCGroup || sc.hasSeveralDescSCGroup) )
					if (layerIdUsed) {
						currLayerId++;
						layerIdUsed = false;
					}
				goThroughSCOfDOMTree(sc);
				prevSC = sc;
			}
		}
	}
	private void goThroughSCOfDOMTree(StackingContext sc) {
		if (drawIdUsed) {
			currDrawId++;
			drawIdUsed = false;
		}
		drawIdUsed = _setDrawId(sc.visualElementInfo, currDrawId);
		layerIdUsed = _setLayerId(sc.visualElementInfo, currLayerId);
		if (sc.stackingContextType == StackingContext.EStackingContextType.SEMI_STACKING_CONTEXT) {
			_goThroughInflowFloatingRelatedGroup(sc.normalFlowBlockBoxes, sc.floatBoxes, sc.normalFlowInlineBoxes);
		}
		else if (sc.stackingContextType == StackingContext.EStackingContextType.STACKING_CONTEXT) {
			_goThroughRootRelatedGroup(sc.rootRelatedSC);
			if (sc.hasSeveralChildSCGroup)
				if (layerIdUsed) {
					currLayerId++;
					layerIdUsed = false;
				}
			goThroughPositionedGroups(sc.negativeSC);
			if (sc.hasSeveralChildSCGroup)
				if (layerIdUsed) {
					currLayerId++;
					layerIdUsed = false;
				}
			_goThroughInflowFloatingRelatedGroup(sc.normalFlowBlockBoxes, sc.floatBoxes, sc.normalFlowInlineBoxes);
			if (sc.hasSeveralChildSCGroup)
				if (layerIdUsed) {
					currLayerId++;
					layerIdUsed = false;
				}
			goThroughPositionedGroups(sc.zeroOrAutoSC);
			if (sc.hasSeveralChildSCGroup)
				if (layerIdUsed) {
					currLayerId++;
					layerIdUsed = false;
				}
			goThroughPositionedGroups(sc.positiveSC);
		}
	}
}
